package pCloud.obligation;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pCloud.PCloudAttributeAndType;
import pCloud.ResourceConnection;
import pCloud.Utilities;
import pCloud.message.DatabaseReply;
import pCloud.message.DatabaseRequest;
import pCloud.server.DatabaseHandle;

import com.sun.xacml.Obligation;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.Result;
import com.sun.xml.internal.ws.client.RequestContext;

public class DataProcessingObligation implements ObligationHandle {

	/*
	 * Process the obligation that contains just the column function and WHERE
	 * clause
	 * 
	 * @see pCloudTest.ObligationProcessor#processObligation(pCloudTest.
	 * ResourceConnection, com.sun.xacml.Obligation,
	 * com.sun.xml.internal.ws.client.RequestContext, java.io.OutputStream)
	 */
	protected String requestedColumnString;

	public Object processObligation(String joiningColumn, DatabaseHandle rc,
			HashMap<URI, Obligation> obligationMap, RequestCtx requestCtx)
			throws Exception {
		// 1. First, construct a select clause
		String selectClause = this.constructSelectClause(obligationMap,
				requestCtx);

		// 2. Next, construct a where clause
		// constructing the WHERE clause from selection-id in the obligation
		String whereClause = "";
		Obligation obligation;
		URI key = new URI(PCloudAttributeAndType.OBLIGATION_SELECTION_ID);

		if (obligationMap == null || !obligationMap.containsKey(key))
			obligation = null;
		else
			obligation = obligationMap.get(key);

		if (obligation != null) {
			String selectionCondition = this.getWhereClause(obligation);
			whereClause = " where " + selectionCondition;
		}

		DatabaseRequest dbr = new DatabaseRequest(selectClause
				+ this.getTableId(requestCtx) + whereClause);
		DatabaseReply result = rc.evaluateRequest(joiningColumn, dbr);
		return result;
		// ResultSet results = (ResultSet)rc.executeQuery(selectClause + tableId
		// + whereClause);
	}

	/**
	 * Check if there's obligation that refines the data, i.e. the query needs
	 * to include the WHERE clause the content of which is returned by this
	 * method
	 * 
	 * @param result
	 * @return
	 * @throws URISyntaxException
	 */
	private String getWhereClause(Obligation obligation)
			throws URISyntaxException {

		List assignments;
		Attribute att;
		assignments = obligation.getAssignments();
		if (assignments.size() == 0)
			return null;
		else {
			// go through all the assignment attribute and apply ones with the
			// ID ...-aggregation-id
			for (int i = 0; i < assignments.size(); i++) {
				att = (Attribute) assignments.get(i);
				if (att.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_SELECTION))) {
					return ((StringAttribute) att.getValue()).getValue();
				}
			}
		}
		return null;
	}

	protected String constructSelectClause(
			HashMap<URI, Obligation> obligationMap, RequestCtx requestCtx)
			throws Exception {
		ArrayList<AttributeValue> requestedCols = Utilities.getAttributeValue(
				PCloudAttributeAndType.RESOURCE_COLUMN_ID,
				requestCtx.getResource());
		if (requestedCols.size() == 0)
			return "select * from ";
		else
			return "select " + columnQuery(obligationMap, requestedCols)
					+ " from ";
	}

	protected String getTableId(RequestCtx requestCtx)
			throws URISyntaxException {
		ArrayList<AttributeValue> requestedTables = Utilities
				.getAttributeValue(PCloudAttributeAndType.RESOURCE_TABLE_ID,
						requestCtx.getResource());
		if (requestedTables.size() != 1) {
			System.err.println("Requested table must be 1");
			return null;
		}
		return ((StringAttribute) requestedTables.get(0)).getValue();
	}

	/**
	 * Check if there's obligation applied to the column. If there is, construct
	 * the part of the query with the constrain
	 * 
	 * Assume that there'll be at most one aggregation function, applied to all
	 * columns
	 * 
	 * @throws Exception
	 */
	private String columnQuery(HashMap<URI, Obligation> obligationMap,
			ArrayList<AttributeValue> columns) throws Exception {

		// First, check the returned obligation for the aggregation function

		Obligation obligation;
		URI key = new URI(PCloudAttributeAndType.OBLIGATION_COLUMN_AGG_ID);
		if (obligationMap == null || !obligationMap.containsKey(key))
			obligation = null;
		else
			obligation = obligationMap.get(key);

		String aggregationFunc = Utilities.getAggregationFunction(obligation);
		String query = "";
		this.requestedColumnString = "";
		if (aggregationFunc == null) {
			for (int i = 0; i < columns.size() - 1; i++) {
				requestedColumnString = requestedColumnString
						+ ((StringAttribute) columns.get(i)).getValue() + "\t";
				query = query + ((StringAttribute) columns.get(i)).getValue()
						+ ", ";
			}
			query = query
					+ ((StringAttribute) columns.get(columns.size() - 1))
							.getValue() + " ";
			this.requestedColumnString = this.requestedColumnString
					+ ((StringAttribute) columns.get(columns.size() - 1))
							.getValue() + "\t";
		} else {
			for (int i = 0; i < columns.size() - 1; i++) {
				query = query + aggregationFunc + "("
						+ ((StringAttribute) columns.get(i)).getValue() + "), ";
				requestedColumnString = requestedColumnString
						+ ((StringAttribute) columns.get(i)).getValue() + "\t";
			}
			query = query
					+ aggregationFunc
					+ "("
					+ ((StringAttribute) columns.get(columns.size() - 1))
							.getValue() + ") ";
			this.requestedColumnString = this.requestedColumnString
					+ ((StringAttribute) columns.get(columns.size() - 1))
							.getValue() + "\t";
		}
		return query;

	}

	// protected void printResultSet(OutputStream out, ResultSet results) throws
	// SQLException{
	// int c = results.getMetaData().getColumnCount();
	// PrintStream ps = new PrintStream(out);
	// ps.println(this.requestedColumnString);
	// while (results.next())
	// {
	// for (int i=1; i<=c; i++)
	// ps.print(results.getObject(i)+"\t");
	// ps.println();
	// }
	// }

	public void printReply(OutputStream out, Object o) throws Exception {
		PrintStream ps = new PrintStream(out);
		ps.println(o);
	}

}
